<?php

require_once './log4php/Logger.php';
require_once './FBConnect.php';
require_once './MySQLConnect.php';
require_once './config.php';
require_once './I18N.php';

Logger::configure('./log4j.properties');

class SituareException extends Exception {
    
}

/**
 * The Situare class provides methods required for the Situare application functionality.
 */
class Situare {

    /**
     *
     * @var Logger
     */
    private $log;

    /**
     *
     * @var FBConnect the FBConnect object
     */
    private $fbConnect;

    /**
     *
     * @var User the logged in user's object
     */
    private $user = null;


    /**
     * Builds up the multi-query's query:
     * 1. get the list of friends using this application (app friends)
     * 2. get mine and app friends user data (name, profile photo url)
     * 3.+4. get mine and app friends location data (lon, lat , timestamp, data)
     *
     * @param string $uid logged in user's uid
     * @param string $extraUserFieldsStr the extra user fields
     * @return string JSON-encoded string
     */
    private static function getQueries($uid, $extraUserFieldsStr) {
        return '{
            "friends" : "SELECT uid FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=' . $uid . ') AND is_app_user",
            "user_data" : "SELECT uid,name,pic_square' . $extraUserFieldsStr . ' FROM user WHERE (uid IN (SELECT uid FROM #friends)) OR uid=' . $uid . '"
        }';
    }

    public function __construct() {
        $this->log = Logger::getLogger(__CLASS__);

        ini_set('arg_separator.output', '&');

        $this->fbConnect = new FBConnect();
        // set up MySQL connection
        MySQLConnect::setConnectionConfig(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB);

    }

    /**
     * Checks whether the current user session is valid.
     * 
     * @return Boolean true - the current session is valid, otherwise false
     */
    public function isValidSession() {
        $this->log->debug("[START] " . __METHOD__);

        $result = FALSE;
        try {
            // the api() throws FacebookApiException in case of invalid session
            $this->fbConnect->api('/me');
            $this->log->info("Valid session");
            $result =  TRUE;
        }
        catch (FacebookApiException $e) {
            $this->log->error(Utils::toString($e));
            $this->log->info("Invalid session");
            $result =  FALSE;
        }

        $this->log->debug("[ END ] " . __METHOD__ . ": " . $result);
        return $result;
    }

    /**
     * Returns the FB login page URL for web clients.
     * 
     * @return string FB login page URL for web clients
     */
    public function getWebLoginURL() {
        return $this->fbConnect->getLoginUrl(array(
            'req_perms' => REQ_PERMS
        ));
    }

    /**
     * Returns the FB login page URL for mobile web clients.
     *
     * @return string FB login page URL for mobile web clients
     */
    public function getMobilWebLoginURL() {
        return $this->fbConnect->getLoginUrl(array(
            'req_perms' => REQ_PERMS,
            'display' => 'popup'
        ));
    }

    /**
     * Returns URL for logout.
     * 
     * @param String $next the url of the next page, after logout.
     * If undefined then the current URL will be used.
     * @return String the logout URL
     */
    public function getLogoutURL($next=NULL) {
        if ($next) {
            return $this->fbConnect->getLogoutUrl(array('next' => $next));
        }
        else {
            return $this->fbConnect->getLogoutUrl();
        }
    }

    /**
     * Returns the logged in user's FB UID.
     *
     * @return string the logged in user's FB UID.
     */
    public function getUID() {
        return $this->fbConnect->getUser();
    }

	/**
	 * Returns received messages.
	 */
	public function getReceivedMessages() {
        $uid = $this->fbConnect->getUser();

        return MySQLConnect::getReceivedMessages($uid);
	}

	/**
	 * Returns sent messages.
	 */
	public function getSentMessages() {
        $uid = $this->fbConnect->getUser();

        return MySQLConnect::getSentMessages($uid);
	}

	/**
	 * Adds message to the DB.
	 * 
	 * @param string $rid receiver ID
	 * @param string $message message text
	 * @param integer $lat message latitude
	 * @param integer $lon message longitude
	 * @param address message address
	 */ 
	public function addMessage($rid, $message, $lat, $lon, $address) {
		$uid = $this->fbConnect->getUser();
		
		MySQLConnect::insertMessage($uid, $rid, $message, $lat, $lon, $address);
	}

	/**
	 * Removes message from the DB.
	 * 
	 * @param string $rid receiver ID
	 * @param string $message message text
	 * @param integer $lat message latitude
	 * @param integer $lon message longitude
	 */ 
	public function removeMessage($mid) {
		$uid = $this->fbConnect->getUser();
		
		MySQLConnect::removeMessage($mid, $uid);
	}
    
    /**
     * Adds tags to the user.
     * 
     * @param $tagNameArray array of tag names
     */ 
    public function addTags($tagNameArray) {
        $uid = $this->fbConnect->getUser();
        
        foreach ($tagNameArray as $tagName)
            MySQLConnect::insertTag($uid, $tagName);
    }

    /**
     * Returns user tags.
     * 
     * @return user tags
     */ 
    public function getTags() {
        $uid = $this->fbConnect->getUser();
        
        return MySQLConnect::getUserTags($uid);
    }

    /**
     * Returns popular tags.
     * 
     * @return popular tags
     */ 
    public function getPopularTags() {
        return MySQLConnect::getPopularTags();
    }
    
    /**
     * Removes tags from the user.
     * 
     * @param $tagNameArray array of tag IDs
     */ 
    public function removeTags($tagIdArray) {
        $uid = $this->fbConnect->getUser();
        
        foreach ($tagIdArray as $tagId)
            MySQLConnect::removeTag($uid, $tagId);
    }
    
    /**
     * Returns friends from the area with same tags.
     * 
     * If distance is null, it is set to 50 (km).
     * 
     * @param $distance maximum distance to friends
     * @return friends
     */ 
    public function getFriendsFromArea($distance) {
		$uid = $this->fbConnect->getUser();
		
		if ($distance == NULL)
			$distance = 50;
		
        return MySQLConnect::getFriendsFromArea($uid, $distance);
    }
    
    /**
     * Returns others from the area with same tags.
     * 
     * Result is limited to 10. If distance is null, it is set to 50 (km).
     * 
     * @param $distance maximum distance to others
     * @return friends
     */ 
    public function getOthersFromArea($distance) {
		$uid = $this->fbConnect->getUser();
		
		if ($distance == NULL)
			$distance = 50;
		
        return MySQLConnect::getOthersFromArea($uid, $distance);
    }

	/**
     * Returns others with tag.
     * 
     * If distance is null, it is set to 50 (km).
     * 
     * @param $tag tag to search
     * @return friends
     */ 
    public function getFriendsWithTag($distance, $tag) {
		$uid = $this->fbConnect->getUser();
		
		if ($distance == NULL)
			$distance = 50;
		
        return MySQLConnect::getFriendsWithTag($uid, $distance, $tag);
    }

	/**
     * Returns friends with tag.
     * 
     * Result is limited to 10. If distance is null, it is set to 50 (km).
     * 
     * @param $tag tag to search
     * @return others
     */ 
    public function getOthersWithTag($distance, $tag) {
		$uid = $this->fbConnect->getUser();
		
		if ($distance == NULL)
			$distance = 50;
		
        return MySQLConnect::getOthersWithTag($uid, $distance, $tag);
    }

    /**
     * Update the logged in user's location.
     * 
     * @param string $lon longitude
     * @param string $lat latitude
     * @param string $note associated note
     * @param string $address address string
     */
    public function updateLocation($lon, $lat, $note, $address) {
        $uid = $this->fbConnect->getUser();

        MySQLConnect::insertLocation($uid, $lon, $lat, $address, $note);

//        // array holding the data to be stored
//        $locationData = array(
//            'lon' => $lon,
//            'lat' => $lat,
//            'address' => $address
//            );
//
//        $associations = $this->fbConnect->data_getAssociatedObjects('user_position', $uid, TRUE);
//        $this->log->debug('Current associations: ' . Utils::toString($associations));
//
//        if (isset($associations) && is_array($associations) && count($associations) > 0) {
//            $this->log->debug('User already has location object -> will be updated.');
//
//            $startTime = microtime(TRUE);
//            // 1. udpate the existing position object
//            $this->fbConnect->data_updateObject($associations[0]['id2'], $locationData, TRUE);
//            // 2. because associations can't be modified, remove the old association
//            $this->fbConnect->data_removeAssociation('user_position', $uid, $associations[0]['id2']);
//            // 3. create new association with new data and timestamp
//            $this->fbConnect->data_setAssociation('user_position', $uid, $associations[0]['id2'], $data, time());
//            $endTime = microtime(TRUE);
//            $this->log->debug("Location update took: " . ($endTime - $startTime) . "ms");
//        } else {
//            // store the data (create new object 'position')
//            $locationObjectId = $this->fbConnect->data_createObject('position', $locationData);
//            $this->log->debug('Newly created "position" object id: ' . $locationObjectId);
//            // create association between the user and position
//            $setAssociationResult = $this->fbConnect->data_setAssociation('user_position', $uid, $locationObjectId, $data, time());
//            $this->log->debug('setAssociationResult: ' . $setAssociationResult);
//        }
    }

    /**
     * Publishes the location update on FB wall as new post.
     * 
     * @param string $message the message associted with the update
     * @param string $address the address string
     * @param string $nextUrl URL where to go after
     */
    public function publishPositionUpdate($message, $address, $nextUrl=HOME_URL) {
        $uid = $this->fbConnect->getUser();
        $feed_action_links = array(
            array(
                'text' => I18N::getMessage('fbfeed.link'),
                'href' => FB_APP_URL
                )
            );

        try {
            $attachment = new stdClass();
            $app_display_info = $this->fbConnect->admin_getAppProperties(
                    array('application_name', 'callback_url', 'logo_url'));
            $attachment->name = $app_display_info['application_name'];
            $attachment->href = FB_APP_URL . '?action=showonmap&fid=' . $uid;
            $logo = new stdClass();
            $logo->type = 'image';
            $logo->src = 'http://client.situare.net/resources/images/icon/situare_icon_25x30.png';
            $logo->href = $app_display_info['callback_url'];
            $attachment->media = array($logo);
            $attachment->properties = array(I18N::getMessage('fbfeed.address') => $address);

            $post_id = $this->fbConnect->stream_publish($message, json_encode($attachment), $feed_action_links);
            $this->log->debug("Feed publish result is: " . $post_id);
        } catch (Exception $e) {
            $this->log->error("Exception: code=" . $e->getCode() . ", message=" . $e->getMessage());
//            if ($e->getCode() == 200) { // missing permission to publish on wall
//                $prompt_perm_url = $this->getPromptPermissionsUrl($nextUrl, 'publish_stream');
//                $this->log->debug("prompt_perm_url: " . $prompt_perm_url);
//                header("Location: $prompt_perm_url");
//            }
            throw $e;
        }
    }

    /**
     * Build up the Permission Prompt page URL.
     *
     * @param string $redirectUrl where to redirect after allow/deny the permission
     * @param string $permissions list of the required permission separated by commas ','
     * @return string the URL string
     */
    public function getPromptPermissionsUrl($redirectUrl, $permissions) {
        return 'https://graph.facebook.com/oauth/authorize?client_id=' . APP_ID . '&redirect_uri=' . $redirectUrl . '&scope=' . $permissions;
    }
    
    /**
     *
     * @param array $extra_fields
     *
     * @return User returns the logged in user's User object
     */
    private function getLoggedInUser($userExtraDataKeys=array()) {
        $this->log->debug('[START] ' . __METHOD__ . ': ' . Utils::toString(func_get_args()));
        
        $extraUserFieldsStr = "";
        if (isset($userExtraDataKeys) && is_array($userExtraDataKeys) && count($userExtraDataKeys) > 0) {
            $extraUserFieldsStr = ',' . implode(',', $userExtraDataKeys);
        }
        $queries = self::getQueries($this->fbConnect->getUser(), $extraUserFieldsStr);
        $result = $this->fbConnect->doMultiquery($queries);

        if (!$result) {
            $this->log->error("Can't get the logged in user's object!");
            $this->log->debug('[ END ] ' . __METHOD__ . ': null');
            return null;
            // TODO: don't return null, but skip DB update
        }

        $friend_array = array();
        $user = null;

        foreach ($result[1]['fql_result_set'] as $user_data) {
            $userExtraDataArray = array();
            foreach ($userExtraDataKeys as $extraDataKey) {
                $userExtraDataArray[$extraDataKey] = $user_data[$extraDataKey];
            }

            if ($user_data['uid'] == $this->fbConnect->getUser()) {
                $user = new User($user_data['uid'], $user_data['name'], $user_data['pic_square']);
                $user->setExtraData($userExtraDataArray);
            } else {
                $friend_array[$user_data['uid']] = new User($user_data['uid'], $user_data['name'], $user_data['pic_square']);
                $friend_array[$user_data['uid']]->setExtraData($userExtraDataArray);
            }
        }

//        if (is_array($result[2]['fql_result_set'])) {
//            foreach ($result[2]['fql_result_set'] as $assoc) {
//                $loc_uid = $assoc['user'];
//                $data = $assoc['_data'];
//                $time = $assoc['_time'];
//
//                foreach ($result[3]['fql_result_set'] as $pos) {
//                    if ($pos['_id'] == $assoc['position']) {
//                        $lon = $pos['lon'];
//                        $lat = $pos['lat'];
//                        $address = $pos['address'];
//
//                        if ($loc_uid == $user->fb_uid && ($user->fb_location == null || $user->fb_location->timestamp < $time)) {
//                            $user->fb_location = new Location($lon, $lat, $time, $data, $address);
//                        } else if (isset($friend_array[$loc_uid]) && ($friend_array[$loc_uid]->fb_location == null || $friend_array[$loc_uid]->fb_location->timestamp < $time)) {
//                            $friend_array[$loc_uid]->fb_location = new Location($lon, $lat, $time, $data, $address);
//                        }
//                        break;
//                    }
//                }
//            }
//        }

//        $user->setFriends($friend_array);
//
//        /* ************************************ */
//        /* Update local DB                      */
//        /* ************************************ */
//
//        // [START]
//
        // build up the array of all users
        $allUsersArray = $friend_array;
        $allUsersArray[$user->fb_uid] = $user;

        // filter the new users from the array of all users (logged in + friends)
        $newUsersArray = MySQLConnect::filterExistingUsers($allUsersArray);
        // if there are any new (not yet present in th DB) users
        if(!empty ($newUsersArray)) {
            // insert all new users (logged in user + friends)
            MySQLConnect::insertUsers($newUsersArray);
            // insert the friendships
            MySQLConnect::insertFriends($user->fb_uid, array_keys($friend_array));
            // insert the locations
            MySQLConnect::insertLocations($allUsersArray);
        }

        // update the outdated users
        // get the list of outdated users
        $outdated_users_array = MySQLConnect::filterOutdatedUsers($allUsersArray);
        // if there are any outdated users (logged in user+friends)
        if (!empty($outdated_users_array)) {
            $this->log->debug("Found some outdated users: " . Utils::toString($outdated_users_array));
            // update the user data of the found outdated users
            foreach ($outdated_users_array as $uid => $user_object) {
                $this->log->debug("Going to update user: $uid");
                MySQLConnect::updateUser($uid, $user_object->fb_name, $user_object->fb_pic_url, $user_object->getExtraData('pic'));
                $this->log->debug("Updating finished, user: $uid");
            }

            $dbFriends = MySQLConnect::getFriends($user->fb_uid);
            MySQLConnect::updateFriends($user->fb_uid, $dbFriends, array_keys($friend_array));

            MySQLConnect::executeInsertOrUpdateOrDelete("UPDATE `user` SET `update`=FALSE WHERE `uid` IN (" . $user->fb_uid . "," . implode(",", $dbFriends) . ");");
        }


 
//        // remove the location object and the association from FB DB
//        foreach ($result[2]['fql_result_set'] as $assoc) {
//            // delete object
//            $this->fbConnect->data_deleteObject($assoc['position']);
//            // delete association
//            $this->fbConnect->data_removeAssociation('user_position', $assoc['user'], $assoc['position']);
//        }

        // [ END ]


        $uid = $this->fbConnect->getUser();
        $userDataArray = MySQLConnect::getAllLocations($uid);

        $loggedInUser = null;
        $friends = array();
        foreach ($userDataArray as $userData) {
            if ($userData['uid'] == $uid) {
                $loggedInUser = new User($userData['uid'], $userData['name'], $userData['pic_square']);
                $loggedInUser->setExtraDataValue('pic', $userData['pic']);

                if (isset($userData['lon']) && isset($userData['lat'])) {
                    $loggedInUser->setLocation(new Location($userData['lon'], $userData['lat'], strtotime($userData['timestamp']), $userData['note'], $userData['address']));
                }
            }
            else {
                $aFriend = new User($userData['uid'], $userData['name'], $userData['pic_square']);
                $aFriend->setExtraDataValue('pic', $userData['pic']);
                if (isset($userData['lon']) && isset($userData['lat'])) {
                    $aFriend->setLocation(new Location($userData['lon'], $userData['lat'], strtotime($userData['timestamp']), $userData['note'], $userData['address']));
                }
                $friends[] = $aFriend;
            }
        }

        $loggedInUser->setFriends($friends);


        $this->log->debug('[ END ] ' . __METHOD__ . ': ' . Utils::toString($loggedInUser));
        return $loggedInUser;
    }

    /**
     *  Returns the logged in user's object.
     *
     * @param array $userExtraDataKeys
     * @return User
     */
    public function getUser($userExtraDataKeys=array()) {
        if (!$this->user) {
            $this->user = $this->getLoggedInUser(array('pic'));
        }

        return $this->user;
    }

    /**
     * Checks whether the page is opened in canvas.
     *
     * @return boolean true is opened in canvas
     */
    public function inCanvas() {
        return isset ($_REQUEST['signed_request']);
    }

    /**
     * {"ResultStatus": "OK", "ResultData": <the_result_data>} or
     * {"ResultStatus": "ERROR", "ResultData": {"ErrorCode" : "<error_code>", "ErrorMsg": "<error_message>"}}
     *
     * @param mixed $response can be Exception or response data array
     */
    public static function getApi20Response($response) {
        $responseArray = array();
        if ($response instanceof Exception) {
            $responseArray['ResultStatus'] = 'ERROR';
            $responseArray['ResultData'] = array();
            $responseArray['ResultData'][JSON_ERROR_CODE] = $response->getCode();
            $responseArray['ResultData'][JSON_ERROR_MSG] = $response->getMessage();
        }
        else {
            $responseArray['ResultStatus'] = 'OK';
            $responseArray['ResultData'] = $response;
        }

        return json_encode($responseArray);
    }
}


?>
